package com.lorizz.picpub.task.impl;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.lorizz.picpub.EntityListener;
import com.lorizz.upm.Task;
import com.lorizz.upm.TaskState;

public class TaskCreatorListener<T> implements EntityListener<T> {

	private Map<EntityAction, List<TaskDescriptor>> taskDescriptors;

	public void setTaskDescriptors(Map<EntityAction, List<TaskDescriptor>> taskDescriptors) {
		this.taskDescriptors = taskDescriptors;
	}

	private Class<?>[] basicTypes = new Class<?>[] { Boolean.class, Boolean.TYPE, Integer.class, Integer.TYPE,
            Double.class, Double.TYPE, Long.class, Long.TYPE, Float.class, Float.TYPE, String.class };

	@Override
	public void notify(T entity, EntityAction action) {
		List<TaskDescriptor> descriptors = taskDescriptors.get(action);
		if (descriptors == null)
			throw new RuntimeException("No task descriptors for action " + action);
		for (TaskDescriptor descriptor : descriptors) {
			try {
				Task task = (Task) entity.getClass().getMethod("create" + descriptor.type).invoke(entity);
				task.setName(descriptor.name);
				task.setDescription(descriptor.description);
				task.setPriority(descriptor.priority);
				task.setState(TaskState.NEW);
				for (String propName : descriptor.properties.keySet()) {
					String propValue = descriptor.properties.get(propName);
					String methodName = "set" + propName.substring(0, 1).toUpperCase() + propName.substring(1);
					Method method = findMethod(task, methodName, basicTypes);
					method.invoke(task, convert(propValue, method.getParameterTypes()[0]));
				}
			} catch (SecurityException e) {
				throw new RuntimeException(e);
			} catch (NoSuchMethodException e) {
				throw new RuntimeException(e);
			} catch (IllegalArgumentException e) {
				throw new RuntimeException(e);
			} catch (IllegalAccessException e) {
				throw new RuntimeException(e);
			} catch (InvocationTargetException e) {
				throw new RuntimeException(e);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
	}
	
    private Method findMethod(Object entity, String name, Class<?>... types) throws Exception {
        Class<?> clz = entity.getClass();
        Method method = null;
        if (types.length == 0) {
            return clz.getMethod(name);
        }
        for (Class<?> c : types) {
            try {
                method = clz.getMethod(name, c);
                break;
            } catch (NoSuchMethodException e) {
            }
        }
        for (Method m : clz.getMethods()) {
            if (m.getName().equals(name)) {
                if (m.getParameterTypes().length > 0 && m.getParameterTypes()[0].isEnum()) {
                    method = m;
                    break;
                }
            }
        }
        if (method == null)
            throw new RuntimeException(String.format("Method %s is not declared for entity class %s", name,
                    clz.getName()));
        return method;
    }
    
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private Object convert(String value, Class<?> toClass) throws Exception {
        if (toClass.equals(Integer.class) || toClass.equals(Integer.TYPE)) {
            return Integer.valueOf(value);
        } else if (toClass.equals(Double.class) || toClass.equals(Double.TYPE)) {
            return Double.valueOf(value);
        } else if (toClass.equals(Long.class) || toClass.equals(Long.TYPE)) {
            return Long.valueOf(value);
        } else if (toClass.equals(Boolean.class) || toClass.equals(Boolean.TYPE)) {
            return Boolean.valueOf(value);
        } else if (toClass.equals(Float.class) || toClass.equals(Float.TYPE)) {
        	return Float.valueOf(value);
        } else if (toClass.isEnum()) {
            return Enum.valueOf((Class) toClass, value);
        }
        return value;
    }

	public static class TaskDescriptor {
		private String type;
		private String name;
		private String description;
		private int priority;
		private Map<String, String> properties = new HashMap<String, String>();

		public void setType(String type) {
			this.type = type;
		}

		public void setName(String name) {
			this.name = name;
		}

		public void setDescription(String description) {
			this.description = description;
		}

		public void setPriority(int priority) {
			this.priority = priority;
		}

		public void setProperties(Map<String, String> properties) {
			this.properties = properties;
		}

	}

}
